﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Script.Serialization;
using System.Text;
using TCCVoigt.Models;
using System.Data;

namespace TCCVoigt.Controllers
{
    public class AlocacaoController : Controller
    {
        private TCCVoigtEntities db = new TCCVoigtEntities();

        //
        // GET: /teste/

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult AlocacaoFull()
        {
            return View();
        }

        public ActionResult Executar()
        {
            //Algoritmo NSGA-II

            //Seleciona solicitacao que estao pendentes de alocacao para programadores
            List<SolicitacaoMudanca> solicitacoes = db.SolicitacaoMudanca
                .Include("Prioridade")
                .Include("Projeto").Include("Requisito").Include("Solicitante")
                .Include("Status").Include("TipoSolicitacao")
                .Where(p => p.Status.Descricao.Equals("Determinar Programador"))
                .ToList();

            //Ordena as solicitacoes de acordo com a prioridade
            solicitacoes = (from s in solicitacoes
                            orderby s.Prioridade.Ordem descending
                            select s).ToList();

            //Seleciona recursos que sao programadores
            List<Recurso> recursos = db.Recurso.Where(p => p.Funcao.Descricao.Equals("Programador")).ToList();

            //Varre a lista e determina a pontuação de cada recurso cruzando com os atributos requeridos na SM.
            foreach (SolicitacaoMudanca sm in solicitacoes)
            {
                /*
                 * Calculo da pontuacao do recurso
                 * (1000 * percentual) pontos para cada conhecimento, 
                 * (100 * percentual) pontos para cada habilidade, 
                 * 10 pontos para cada competência
                */
                foreach (Recurso recurso in recursos)
                {
                    //Zero-se as pontuacoes para fins de controle.
                    recurso.Dias = 0;
                    recurso.PontuacaoP1 = 0;
                    recurso.PontuacaoP2 = 0;
                    recurso.Pontuacao = 0;

                    List<RecursoConhecimento> co = recurso.RecursoConhecimento.ToList();
                    foreach (RecursoConhecimento conhecimento in co)
                    {
                        if (conhecimento.CodigoModulo == sm.Requisito.CodigoModulo)
                        {
                            recurso.PontuacaoP1 += (Convert.ToDouble(conhecimento.Percentual) / 100) * 1000;
                        }
                    }
                    List<RecursoHabilidade> ha = recurso.RecursoHabilidade.ToList();
                    foreach (RecursoHabilidade habilidade in ha)
                    {
                        foreach (ModuloHabilidade smHab in sm.Requisito.Modulo.ModuloHabilidade.ToList())
                        {
                            if (smHab.Habilidade == habilidade.Habilidade)
                            {
                                recurso.PontuacaoP1 += (Convert.ToDouble(habilidade.Percentual) / 100) * 100;
                            }
                        }
                    }
                    List<RecursoAtitude> comp = recurso.RecursoAtitude.ToList();
                    foreach (RecursoAtitude atitude in comp)
                    {
                        foreach (ModuloAtitude smComp in sm.Requisito.Modulo.ModuloAtitude.ToList())
                        {
                            if (smComp.Atitude == atitude.Atitude)
                            {
                                recurso.PontuacaoP1 += 10;
                            }
                        }
                    }
                }
                List<Recurso> recursosVisitados = new List<Recurso>(); //Recursos visitados
                List<Recurso> A = new List<Recurso>(); //Recursos dominante
                List<Recurso> B = new List<Recurso>(); //Recursos dominados
                List<Recurso> G = new List<Recurso>(); //Recursos de geração


                //Zera as listas para a proxima iteracao de SM.
                recursosVisitados = null;
                recursosVisitados = new List<Recurso>();
                A = null;
                A = new List<Recurso>();
                B = null;
                B = new List<Recurso>();
                G = null;
                G = new List<Recurso>();

                //Le primeiro recurso da lista
                foreach (Recurso R in recursos)
                {
                    recursosVisitados.Add(R);
                    A.Add(R);
                    //Itera sobre a lista de recursos, desconsiderando o recurso atual do foreach acima
                    foreach (Recurso Q in recursos)
                    {
                        //Ignora os recursos ja visitados.
                        if (recursosVisitados.Contains(Q))
                        {
                            continue;
                        }
                        //Ignora se o recurso tiver pontuacao zerada
                        if (Q.PontuacaoP1 == 0)
                        {
                            continue;
                        }
                        /*
                         * Para cada recurso Q, deve gerar uma lista de dominantes e dominados
                         * Se a pontuacao do recurso Q > R, recurso é dominante, entao insere na lista A;
                         * Se a pontuacao do recurso Q < R, recurso é dominado, então insere na lista B;
                         * Se a pontuacao do recurso for igual, adiciona R na lista de geracao G.
                         */
                        if (Q.PontuacaoP1 > R.PontuacaoP1)
                        {
                            if (!A.Contains(Q))
                                A.Add(Q);
                        }
                        else if (Q.PontuacaoP1 < R.PontuacaoP1)
                        {
                            if (!B.Contains(Q))
                                B.Add(Q);
                        }
                        else
                        {
                            if (!G.Contains(Q))
                                G.Add(Q);
                        }
                    }
                    //Se não tiver nenhum recurso para comparação que tenha pontuação,
                    //O primeiro recurso deve ser adicionado na lista de geração.
                    if (A.Count == 0 && B.Count == 0 && G.Count == 0 && R.PontuacaoP1 > 0)
                    {
                        G.Add(R);
                    }
                }
                //Ordena a lista de dominantes pela pontuação
                A = A.OrderByDescending(p => p.PontuacaoP1).ToList();

                //Ordena a lista de geração pela pontuação
                G = G.OrderByDescending(p => p.PontuacaoP1).ToList();

                foreach (Recurso recurso in A)
                {
                    //Zero as pontuacoes para fins de controle.
                    recurso.Dias = 0;
                    recurso.PontuacaoP2 = 0;
                    recurso.Pontuacao = 0;
                }

                //Crowding Distance Dominantes
                CrowdingDistance(A, sm);

                //Ordena Crowd
                OrdenaCrowd(A);

                //Calcula a pontuacao considerando os dias
                CalculaRank(A);

                //Uniao das duas listas de Geracao e Dominantes
                List<Recurso> S = A.Union(G).ToList();

                //Ordena as duas listas
                S = S.OrderByDescending(p => p.Pontuacao).ToList();

                //Valida se encontrou solução
                if (S.Count > 0)
                {
                    //Determina o melhor recurso para a tarefa (primeiro da lista)
                    Recurso recSelecionado = S.First();

                    //Gera a alocação fisica da Solicitação de Mudança para o recurso
                    GravarAlocacao(sm.Codigo, recSelecionado.Codigo);
                }

                //Fim da alocação, continua com a proxima solicitação de mudança.
            }
            ViewBag.Message = "Alocação realizada! Visualize a alocão no proximo item do menu.";
            return View();
        }

        private void CalculaRank(List<Recurso> A)
        {
            Random rnd = new Random();
            foreach (Recurso r in A)
            {
                //definido um numero aleatorio de pontuacao
                r.PontuacaoP2 = rnd.Next(500, 800);
                //calculo do rank
                r.PontuacaoP2 = r.PontuacaoP2 + (r.Dias * (-100));
                //pontuacao final do recurso
                r.Pontuacao = r.PontuacaoP1 + r.PontuacaoP2;
            }
        }

        private void CrowdingDistance(List<Recurso> A, SolicitacaoMudanca sm)
        {
            foreach (Recurso r in A)
            {
                CalculaDiasInicio(r, sm.PrazoCliente);
            }
        }

        /// <summary>
        /// Calcula a quantidade de dias para iniciar a solicitacao
        /// </summary>
        /// <param name="r"></param>
        /// <param name="prazoCliente"></param>
        private void CalculaDiasInicio(Recurso r, DateTime? prazoCliente)
        {
            int dias = 0;
            DateTime? dataUltimaAlocacao = null;
            try
            {
                dataUltimaAlocacao = r.AlocacaoRecurso.OrderByDescending(p => p.DataFim).FirstOrDefault().DataFim;
            }
            catch
            {
                dataUltimaAlocacao = null;
            }

            if (dataUltimaAlocacao.HasValue)
            {
                TimeSpan diffResult = dataUltimaAlocacao.Value.Date.Subtract(DateTime.Today.Date);
                if (diffResult.Days > 0)
                    dias = diffResult.Days;
            }
            r.Dias = dias;
        }

        /// <summary>
        /// Metodo que ordena a lista de dominantes conforme a quantidade de dias para iniciar a Solicitacao
        /// </summary>
        /// <param name="A"></param>
        private void OrdenaCrowd(List<Recurso> A)
        {
            A = A.OrderBy(p => p.Dias).ToList();
        }

        public JsonResult getAlocacao()
        {
            List<Alocacao> lstAlocacao = new List<Alocacao>();
            List<Solicitacoes> lstSolicitacoes = new List<Solicitacoes>();
            List<Feriado> feriados = db.Feriado.ToList();
            List<Recurso> recursos = db.Recurso.Where(p => p.Funcao.Descricao.Equals("Programador")).OrderBy(p => p.Descricao).ToList();
            List<Restricao> restricoes = null;
            List<AlocacaoRecurso> alocacoes = null;
            foreach (Recurso recurso in recursos)
            {
                restricoes = db.Restricao.Where(p => p.CodigoRecurso == recurso.Codigo).ToList();
                alocacoes = db.AlocacaoRecurso.Include("SolicitacaoMudanca").Where(p => p.CodigoRecurso == recurso.Codigo).ToList();
                lstSolicitacoes = null;
                lstSolicitacoes = new List<Solicitacoes>();
                DateTime limiteInicio = DateTime.Now.AddDays(-60);
                DateTime limiteFim = DateTime.Now.AddDays(60);
                foreach (Feriado feriado in feriados.Where(p => p.DataInicio >= limiteInicio && p.DataFim <= limiteFim))
                {
                    lstSolicitacoes.Add(new Solicitacoes("/Date(" + ConvertToTimestamp(feriado.DataInicio) + ")/", "/Date(" + ConvertToTimestamp(feriado.DataFim) + ")/", "<b>" + feriado.Descricao + "</b>", "ganttRed", "F"));
                }
                foreach (Restricao restricao in restricoes)
                {
                    lstSolicitacoes.Add(new Solicitacoes("/Date(" + ConvertToTimestamp(restricao.DataInicio.Value) + ")/", "/Date(" + ConvertToTimestamp(restricao.DataFim.Value) + ")/", "<b>" + restricao.Descricao + "</b>", "ganttOrange", "R"));
                }
                foreach (AlocacaoRecurso alocacao in alocacoes)
                {
                    lstSolicitacoes.Add(new Solicitacoes("/Date(" + ConvertToTimestamp(alocacao.DataInicio) + ")/", "/Date(" + ConvertToTimestamp(alocacao.DataFim) + ")/", "<b>SMS " + alocacao.SolicitacaoMudanca.Numero + "</b>", "ganttGreen", "S"));
                }
                lstAlocacao.Add(new Alocacao(recurso.Descricao, "", lstSolicitacoes));
            }
            JsonResult result = new JsonResult();
            result.Data = lstAlocacao;
            result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
            return result;
        }

        public ActionResult AlocacaoManual(int id = 0)
        {

            ViewBag.CodigoSolicitacaoMudanca =
                new SelectList((from s in db.SolicitacaoMudanca.ToList()
                                where s.CodigoStatus == 10
                                select new
                                {
                                    Codigo = s.Codigo,
                                    Descricao = s.Numero + " - " + s.Solicitacao
                                }),
                    "Codigo",
                    "Descricao",
                    id > 0 ? id : 0);

            ViewBag.CodigoRecurso = new SelectList(db.Recurso.OrderBy(p => p.Descricao), "Codigo", "Descricao");

            return View();
        }

        [HttpPost]
        public ActionResult AlocacaoManual(FormCollection form)
        {
            int codigoSolicitacaoMudanca = Convert.ToInt32(form["CodigoSolicitacaoMudanca"]);
            int codigoRecurso = Convert.ToInt32(form["CodigoRecurso"]);

            GravarAlocacao(codigoSolicitacaoMudanca, codigoRecurso);

            return RedirectToAction("Index");
        }

        public void GravarAlocacao(int codigoSolicitacaoMudanca, int codigoRecurso)
        {
            AlocacaoRecurso alocacao = db.AlocacaoRecurso
                                        .Where(p => p.CodigoRecurso == codigoRecurso)
                                        .OrderByDescending(p => p.DataFim)
                                        .FirstOrDefault();

            SolicitacaoMudanca solicitacao = db.SolicitacaoMudanca.Single(c => c.Codigo == codigoSolicitacaoMudanca);
            Recurso recurso = db.Recurso.Single(c => c.Codigo == codigoRecurso);

            AlocacaoRecurso aloca = new AlocacaoRecurso();
            DateTime data = new DateTime();
            if (alocacao == null)
            {
                data = DiaUtil(DateTime.Today.AddHours(8), recurso);
            }
            else
            {
                //aloca no proximo dia util - trocar por data fim!!
                data = alocacao.DataFim;
                if (data.Hour == 18)
                {
                    //se a hora é igual a 18, entao é final do dia, adiciona um dia util.
                    data = DiaUtil(data.AddDays(1), recurso);
                    data = new DateTime(data.Year, data.Month, data.Day, 8, 0, 0);
                }
                else
                {
                    data = DiaUtil(data, recurso);
                }
            }
            decimal dias = (Convert.ToDecimal(solicitacao.HoraProgramacao.Value) / 
                            (Convert.ToDecimal(recurso.HorasDia) + Convert.ToDecimal(0.5)));
            decimal hora = 0;
            for (int i = 1; i <= decimal.Truncate(dias); i++)
            {
                //a cada iteracao valida se é dia util.
                data = DiaUtil(data, recurso);
                if (data.Hour == 18)
                {
                    //se a hora é igual a 18, entao é final do dia, adiciona um dia util.
                    data = DiaUtil(data.AddDays(1), recurso);
                    data = new DateTime(data.Year, data.Month, data.Day, 8, 0, 0);
                }
                if (data.Hour == 8)
                {
                    #region alocacaoNormal
                    //manha
                    aloca = new AlocacaoRecurso();
                    aloca.CodigoRecurso = recurso.Codigo;
                    aloca.CodigoSolicitacaoMudanca = solicitacao.Codigo;
                    aloca.DataInicio = data;
                    data = data.AddHours(4);
                    aloca.DataFim = data;
                    db.AlocacaoRecurso.AddObject(aloca);
                    db.SaveChanges();
                    //tarde
                    aloca = new AlocacaoRecurso();
                    aloca.CodigoRecurso = recurso.Codigo;
                    aloca.CodigoSolicitacaoMudanca = solicitacao.Codigo;
                    data = data.AddHours(1).AddMinutes(30);
                    aloca.DataInicio = data;
                    data = data.AddHours(4).AddMinutes(30);
                    aloca.DataFim = data;
                    db.AlocacaoRecurso.AddObject(aloca);
                    db.SaveChanges();
                    #endregion
                }
                else
                {
                    //senao, faz o calculo para alocacao no periodo correto.
                    if (data.Hour > 12)
                    {
                        #region manha
                        hora = 4 + (Convert.ToDecimal(4.5) - (18 - data.Hour));
                        aloca = new AlocacaoRecurso();
                        aloca.CodigoRecurso = recurso.Codigo;
                        aloca.CodigoSolicitacaoMudanca = solicitacao.Codigo;
                        aloca.DataInicio = data;
                        aloca.DataFim = data;
                        if (data.Minute == 30)
                        {
                            data = data.AddMinutes(30);
                        }
                        data = data.AddHours(18 - data.Hour);
                        aloca.DataFim = data;
                        db.AlocacaoRecurso.AddObject(aloca);
                        db.SaveChanges();
                        #endregion
                    }
                    else
                    {
                        #region manhaetarde
                        aloca = new AlocacaoRecurso();
                        aloca.CodigoRecurso = recurso.Codigo;
                        aloca.CodigoSolicitacaoMudanca = solicitacao.Codigo;
                        hora = data.Hour - 8;
                        aloca.DataInicio = data;
                        data = data.AddHours(12 - data.Hour);
                        aloca.DataFim = data;
                        db.AlocacaoRecurso.AddObject(aloca);
                        db.SaveChanges();
                        //tarde
                        aloca = new AlocacaoRecurso();
                        aloca.CodigoRecurso = recurso.Codigo;
                        aloca.CodigoSolicitacaoMudanca = solicitacao.Codigo;
                        data = data.AddHours(1).AddMinutes(30);
                        aloca.DataInicio = data;
                        data = data.AddHours(4).AddMinutes(30);
                        aloca.DataFim = data;
                        db.AlocacaoRecurso.AddObject(aloca);
                        db.SaveChanges();
                        #endregion
                    }
                }
            }
            if (dias % 1 > 0)
            {
                hora += solicitacao.HoraProgramacao.Value - 
                    ((recurso.HorasDia + Convert.ToDecimal(0.5)) * decimal.Truncate(dias));
                if (data.Hour == 18)
                {
                    //se a hora é igual a 18, entao é final do dia, adiciona um dia util.
                    data = DiaUtil(data.AddDays(1), recurso);
                    data = new DateTime(data.Year, data.Month, data.Day, 8, 0, 0);
                }
                //verifica a hora da data e adiciona horas
                if (data.Hour > 12)
                    hora += 4 + (Convert.ToDecimal(4.5) - (18 - data.Hour));
                else
                    hora += data.Hour - 8;

                if (data.Hour == 8)
                {
                    #region alocacaoComecodoDia
                    //alocacao normal
                    
                    if (hora >= Convert.ToDecimal(8.5))
                    {
                        aloca = new AlocacaoRecurso();
                        aloca.CodigoRecurso = recurso.Codigo;
                        aloca.CodigoSolicitacaoMudanca = solicitacao.Codigo;
                        //manha
                        aloca.DataInicio = data;
                        data = data.AddHours(4);
                        aloca.DataFim = data;
                        db.AlocacaoRecurso.AddObject(aloca);
                        db.SaveChanges();
                        //tarde
                        aloca = new AlocacaoRecurso();
                        aloca.CodigoRecurso = recurso.Codigo;
                        aloca.CodigoSolicitacaoMudanca = solicitacao.Codigo;
                        data = data.AddHours(1).AddMinutes(30);
                        aloca.DataInicio = data;
                        aloca.DataFim = data.AddHours(4).AddMinutes(30);
                        db.AlocacaoRecurso.AddObject(aloca);
                        db.SaveChanges();

                        //diminui um dia e faz o restante da alocacao.
                        hora = (hora - Convert.ToDecimal(8.5));
                    }

                    aloca = new AlocacaoRecurso();
                    aloca.CodigoRecurso = recurso.Codigo;
                    aloca.CodigoSolicitacaoMudanca = solicitacao.Codigo;
                    //manha
                    aloca.DataInicio = data;
                    if (hora > 4)
                    {
                        data = data.AddHours(4);
                        aloca.DataFim = data;
                        db.AlocacaoRecurso.AddObject(aloca);
                        db.SaveChanges();
                        //tarde
                        aloca = new AlocacaoRecurso();
                        aloca.CodigoRecurso = recurso.Codigo;
                        aloca.CodigoSolicitacaoMudanca = solicitacao.Codigo;
                        data = data.AddHours(1).AddMinutes(30);
                        aloca.DataInicio = data;
                        if (hora % 1 > 0)
                            aloca.DataFim = data.AddHours(Convert.ToDouble(hora - 4)).AddMinutes(30);
                        else
                            aloca.DataFim = data.AddHours(Convert.ToDouble(hora - 4));

                        db.AlocacaoRecurso.AddObject(aloca);
                        db.SaveChanges();
                    }
                    else
                    {
                        aloca.DataFim = data.AddHours(Convert.ToDouble(hora));
                        db.AlocacaoRecurso.AddObject(aloca);
                        db.SaveChanges();
                    }
                    #endregion
                }
                else
                {
                    #region alocacaoporhora
                    //faz a alocacao e diminui das horas até zerar.
                    while (hora >0)
                    {
                        if (data.Hour == 18)
                        {
                            //se a hora é igual a 18, entao é final do dia, adiciona um dia util.
                            data = DiaUtil(data.AddDays(1), recurso);
                            data = new DateTime(data.Year, data.Month, data.Day, 8, 0, 0);
                        }
                        aloca = new AlocacaoRecurso();
                        aloca.CodigoRecurso = recurso.Codigo;
                        aloca.CodigoSolicitacaoMudanca = solicitacao.Codigo;
                        if (data.Hour == 12)
                            data = data.AddHours(1).AddMinutes(30);

                        decimal horaInicial = data.Hour;
                        decimal minutoInicial = data.Minute > 0 ? Convert.ToDecimal(0.5) : 0;
                        aloca.DataInicio = data;
                        if (data.Hour < 12)
                        {
                            data = new DateTime(data.Year, data.Month, data.Day, 12, 0, 0);
                            aloca.DataFim = data;
                            hora -= 12 - horaInicial - minutoInicial;

                            //adiciona as horas da tarde
                        }
                        else
                        {
                            if (hora <= (18 - data.Hour))
                            {
                                decimal horaAtual = data.Hour;
                                data = data.AddHours(Convert.ToDouble(hora));
                                aloca.DataFim = data;
                                hora -= data.Hour - horaAtual;
                            }
                            else
                            {
                                hora -= 18 - data.Hour;
                                data = new DateTime(data.Year, data.Month, data.Day, 18, 0, 0);
                                aloca.DataFim = data;
                            }
                        }
                        db.AlocacaoRecurso.AddObject(aloca);
                        db.SaveChanges();
                    }
                    #endregion
                }
            }
            //Apos alocar, define a Solicitação de Mudança como Aguardando Programação.
            solicitacao.CodigoStatus = db.Status.Single(c => c.Descricao.Equals("Aguardando Programação")).Codigo;
            db.SolicitacaoMudanca.ApplyCurrentValues(solicitacao);
            db.ObjectStateManager.ChangeObjectState(solicitacao, EntityState.Modified);
            db.SaveChanges();
        }

        private string ConvertToTimestamp(DateTime value)
        {
            TimeSpan span = (value - new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime());
            return span.TotalSeconds + "000";
        }

        public DateTime DiaUtil(DateTime dt, Recurso r)
        {
            if (dt.DayOfWeek == DayOfWeek.Saturday)
            {
                dt = dt.AddDays(2);
                return DiaUtil(dt, r);
            }
            else if (dt.DayOfWeek == DayOfWeek.Sunday)
            {
                dt = dt.AddDays(1);
                return DiaUtil(dt, r);
            }
            else if (Feriado(dt) == true)
            {
                dt = dt.AddDays(1);
                return DiaUtil(dt, r);
            }
            else if (Restricao(dt, r) == true)
            {
                dt = dt.AddDays(1);
                return DiaUtil(dt, r);
            }
            else
            {
                return dt;
            }
        }

        private bool Restricao(DateTime dt, Recurso r)
        {
            List<Restricao> restricoes = db.Restricao.Where(p => p.CodigoRecurso == r.Codigo).ToList();
            foreach (Restricao restricao in restricoes)
            {
                if (dt >= restricao.DataInicio && dt <= restricao.DataFim)
                {
                    return true;
                }
            }
            return false;
        }

        public bool Feriado(DateTime dt)
        {
            List<Feriado> feriados = db.Feriado.ToList();
            foreach (Feriado feriado in feriados)
            {
                if (feriado.DataFim.Date == dt.Date)
                {
                    return true;
                }
            }
            return false;            
        }
    }

}
